#pragma once

#include <map>
#include <unordered_map>
#include <functional>
#include <memory>
#include <atomic>
#include <condition_variable>
#include <thread>


struct epoll_event;

namespace evm { namespace event {

using namespace std;

const int EV_NONE = 0;
const int EV_READABLE = 1;
const int EV_WRITABLE = 2;
const int EV_ALL = EV_READABLE | EV_WRITABLE;

const int EV_NOMORE = -1;
const int EV_PERIODIC = 0;

const size_t EV_INVALID_ID = 0;

const int EV_MAX_EVENTS = 8;  // default epoll max events

/* time event handler type
 * param: timer id
 * returns: next event time interval; if EV_NOMORE, stop;
 *          if EV_PERIODIC, continue to use last interval
 */
using TimeHandler = function<double (size_t id)>;

/* file event handler type
 * param: file descriptor, event mask
 * returns: void
 */
using FileHandler = function<void (int fd, int mask)>;

struct EventLoop {
private:
    struct FileEvent {
        int mask{EV_NONE};

        FileHandler rhandler;

        FileHandler whandler;

        bool same_rw;
    };

    struct TimeEvent {
        size_t id;  // time event identifier

        long when;  // in milliseconds

        /* Note: this handler should not remove this or other *time events*,
         * otherwise, internal data structures may be destructed. */
        TimeHandler handler;

        double interval;  // next event time interval, in seconds
    };

    unordered_map<int, FileEvent> file_events_;
    map<long, TimeEvent> time_events_;

public:
    EventLoop(unsigned maxevents = EV_MAX_EVENTS);

    ~EventLoop();

    bool add_event(int fd, int mask, FileHandler handler);

    bool rmv_event(int fd, int mask);

    size_t add_timer(double seconds, TimeHandler handler);

    bool rmv_timer(size_t id);

    /* Run event loop forever, until being stopped. */
    void run();

    /* Start the event loop in a separate thread. */
    void start();

    void stop(bool wait = true);

    bool is_running() {
        return running_;
    }

private:
    void process_file_events();

    void process_time_events();

    int epoll_fd_;
    epoll_event *epoll_events_;
    unsigned epoll_maxevents_;
    size_t next_time_id_{};

    atomic<bool> running_{};
    /* The mutex will allow dynamic event adding and removing when event loop is running.
     * It will also help for stopping the event loop. */
    mutex mutex_;
    condition_variable cv_;

    thread thread_;
};

using EventLoopPtr = shared_ptr<EventLoop>;

long time();

}}
